package com.bangcommunity.bbframe.common.utils.id;

import com.bangcommunity.bbframe.common.utils.DateUtils;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Calendar;
import java.util.Date;

/**
 *
 * @author tanghc
 * @date 17/1/22 自生成Id生成器.
 *
 *       <p>
 *       长度为64bit,从高位到低位依次为
 *       </p>
 *
 *       <pre>
 * 1bit   符号位
 * 41bits 时间偏移量从2016年11月1日零点到现在的毫秒数
 * 10bits 工作进程Id
 * 12bits 同一个毫秒内的自增量
 *       </pre>
 *
 *       <p>
 *       工作进程Id获取优先级: 系统变量{@code sjdbc.self.id.generator.worker.id} 大于
 *       环境变量{@code SJDBC_SELF_ID_GENERATOR_WORKER_ID}
 *       ,另外可以调用@{@code CommonSelfIdGenerator.setWorkerId}进行设置
 *       </p>
 *
 *       摘自ddframe CommonSelfIdGenerator
 */
public class DangDangIdGenerator {
    static Logger logger = LoggerFactory.getLogger(DangDangIdGenerator.class);
    public final long SJDBC_EPOCH;

    private final long SEQUENCE_BITS = 12L;

    private static final long WORKER_ID_BITS = 10L;

    private final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

    private final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

    private final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;

    public static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;
    private static final String WORKER_ID_SYS_PROP_KEY = "sjdbc.self.id.generator.worker.id";

    private AbstractClock clock = AbstractClock.systemClock();

    private long workerId;

    public DangDangIdGenerator() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2016, Calendar.NOVEMBER, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        SJDBC_EPOCH = calendar.getTimeInMillis();
        initWorkerId();
    }

    private long sequence;

    private long lastTime;

    void initWorkerId() {
        String workerId = System.getProperty(WORKER_ID_SYS_PROP_KEY);
        if (!Strings.isNullOrEmpty(workerId)) {
            setWorkerId(Long.valueOf(workerId));
            return;
        }
        workerId = System.getenv(WORKER_ID_SYS_PROP_KEY.toUpperCase());
        if (Strings.isNullOrEmpty(workerId)) {
            return;
        }
        setWorkerId(Long.valueOf(workerId));
    }

    /**
     * 设置工作进程Id.
     *
     * @param workerId
     *            工作进程Id
     */
    public void setWorkerId(final Long workerId) {
        Preconditions.checkArgument(workerId >= 0L && workerId < WORKER_ID_MAX_VALUE);
        this.workerId = workerId;
    }

    /**
     * 生成Id.
     *
     * @return 返回@{@link Long}类型的Id
     */
    public synchronized long generateId() {
        long time = clock.millis();
        Preconditions.checkState(lastTime <= time,
                "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastTime,
                time);
        if (lastTime == time) {
            if (0L == (++sequence & SEQUENCE_MASK)) {
                time = waitUntilNextTime(time);
            }
        } else {
            sequence = 0;
        }
        lastTime = time;
//        if (logger.isDebugEnabled()) {
//            logger.debug("{}-{}-{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(lastTime)),
//                    workerId, sequence);
//        }
        return ((time - SJDBC_EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }

    public String idForHuman(long id) {
        long hig = -1 << TIMESTAMP_LEFT_SHIFT_BITS;
        long low = (1 << SEQUENCE_BITS) - 1;
        long mid = ~(hig | low);
        long hig42 = (hig & id) >> TIMESTAMP_LEFT_SHIFT_BITS;
        long low12 = (low & id);
        long mid10 = (mid & id) >> SEQUENCE_BITS;
        return DateUtils.format(new Date(hig42 + SJDBC_EPOCH), "yyyyMMddHHMMSS") + "_" + mid10 + "_" + low12;
    }

    private long waitUntilNextTime(final long lastTime) {
        long time = clock.millis();
        while (time <= lastTime) {
            time = clock.millis();
        }
        return time;
    }
}